<!doctype html>
<html>
  <head>
    <meta charset=utf-8>
    <title>Tests for the effect of setting a CSS transition's
           Animation.currentTime</title>
    <style>

.animated-div {
  margin-left: 100px;
  transition: margin-left 1000s linear 1000s;
}

    </style>
    <script src="../testcommon.js"></script>
  </head>
  <body>
    <script type="text/javascript">

'use strict';

// TODO: Once the computedTiming property is implemented, add checks to the
// checker helpers to ensure that computedTiming's properties are updated as
// expected.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055


const ANIM_DELAY_MS = 1000000; // 1000s
const ANIM_DUR_MS = 1000000; // 1000s

/**
 * These helpers get the value that the currentTime needs to be set to, to put
 * an animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into
 * the middle of various phases or points through the active duration.
 */
function currentTimeForBeforePhase() {
  return ANIM_DELAY_MS / 2;
}
function currentTimeForActivePhase() {
  return ANIM_DELAY_MS + ANIM_DUR_MS / 2;
}
function currentTimeForAfterPhase() {
  return ANIM_DELAY_MS + ANIM_DUR_MS + ANIM_DELAY_MS / 2;
}
function currentTimeForStartOfActiveInterval() {
  return ANIM_DELAY_MS;
}
function currentTimeForFiftyPercentThroughActiveInterval() {
  return ANIM_DELAY_MS + ANIM_DUR_MS * 0.5;
}
function currentTimeForEndOfActiveInterval() {
  return ANIM_DELAY_MS + ANIM_DUR_MS;
}


// Expected computed 'margin-left' values at points during the active interval:
// When we assert_between_inclusive using these values we could in theory cause
// intermittent failure due to very long delays between paints, but since the
// active duration is 1000s long, a delay would need to be around 100s to cause
// that. If that's happening then there are likely other issues that should be
// fixed, so a failure to make us look into that seems like a good thing.
const INITIAL_POSITION = 100;
const TEN_PCT_POSITION = 110;
const FIFTY_PCT_POSITION = 150;
const END_POSITION = 200;


// The terms used for the naming of the following helper functions refer to
// terms used in the Web Animations specification for specific phases of an
// animation. The terms can be found here:
//
//   http://w3c.github.io/web-animations/#animation-effect-phases-and-states

// Called when currentTime is set to zero (the beginning of the start delay).
function checkStateOnSettingCurrentTimeToZero(animation)
{
  // We don't test animation.currentTime since our caller just set it.

  assert_equals(animation.playState, 'running',
    'Animation.playState should be "running" at the start of ' +
    'the start delay');

  assert_equals(animation.effect.target.style.animationPlayState, 'running',
    'Animation.effect.target.style.animationPlayState should be ' +
    '"running" at the start of the start delay');

  var div = animation.effect.target;
  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
  assert_equals(marginLeft, UNANIMATED_POSITION,
                'the computed value of margin-left should be unaffected ' +
                'at the beginning of the start delay');
}

// Called when the ready Promise's callbacks should happen
function checkStateOnReadyPromiseResolved(animation)
{
  // the 0.0001 here is for rounding error
  assert_less_than_equal(animation.currentTime,
    animation.timeline.currentTime - animation.startTime + 0.0001,
    'Animation.currentTime should be less than the local time ' +
    'equivalent of the timeline\'s currentTime on the first paint tick ' +
    'after animation creation');

  assert_equals(animation.playState, 'running',
    'Animation.playState should be "running" on the first paint ' +
    'tick after animation creation');

  var div = animation.effect.target;
  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
  assert_equals(marginLeft, INITIAL_POSITION,
                'the computed value of margin-left should be unaffected ' +
                'by an animation with a delay on ready Promise resolve');
}

// Called when currentTime is set to the time the active interval starts.
function checkStateAtActiveIntervalStartTime(animation)
{
  // We don't test animation.currentTime since our caller just set it.

  assert_equals(animation.playState, 'running',
    'Animation.playState should be "running" at the start of ' +
    'the active interval');

  var div = animation.effect.target;
  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
  assert_between_inclusive(marginLeft, INITIAL_POSITION, TEN_PCT_POSITION,
    'the computed value of margin-left should be close to the value at the ' +
    'beginning of the animation');
}

function checkStateAtFiftyPctOfActiveInterval(animation)
{
  // We don't test animation.currentTime since our caller just set it.

  var div = animation.effect.target;
  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
  assert_equals(marginLeft, FIFTY_PCT_POSITION,
    'the computed value of margin-left should be half way through the ' +
    'animation at the midpoint of the active interval');
}

// Called when currentTime is set to the time the active interval ends.
function checkStateAtActiveIntervalEndTime(animation)
{
  // We don't test animation.currentTime since our caller just set it.

  assert_equals(animation.playState, 'finished',
    'Animation.playState should be "finished" at the end of ' +
    'the active interval');

  var div = animation.effect.target;
  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
  assert_equals(marginLeft, END_POSITION,
    'the computed value of margin-left should be the final transitioned-to ' +
    'value at the end of the active duration');
}

test(function(t)
{
  var div = addDiv(t, {'class': 'animated-div'});
  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];
  assert_equals(animation.currentTime, 0, 'currentTime should be zero');
}, 'currentTime of a newly created transition is zero');


test(function(t)
{
  var div = addDiv(t, {'class': 'animated-div'});
  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];

  // So that animation is running instead of paused when we set currentTime:
  animation.startTime = animation.timeline.currentTime;

  animation.currentTime = 10;
  assert_equals(animation.currentTime, 10,
    'Check setting of currentTime actually works');
}, 'Sanity test to check round-tripping assigning to new animation\'s ' +
   'currentTime');


async_test(function(t) {
  var div = addDiv(t, {'class': 'animated-div'});
  var eventWatcher = new EventWatcher(t, div, 'transitionend');

  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];

  animation.ready.then(t.step_func(function() {
    checkStateOnReadyPromiseResolved(animation);

    animation.currentTime = currentTimeForStartOfActiveInterval();
    checkStateAtActiveIntervalStartTime(animation);

    animation.currentTime = currentTimeForFiftyPercentThroughActiveInterval();
    checkStateAtFiftyPctOfActiveInterval(animation);

    animation.currentTime = currentTimeForEndOfActiveInterval();
    return eventWatcher.wait_for('transitionend');
  })).then(t.step_func(function() {
    checkStateAtActiveIntervalEndTime(animation);
  })).catch(t.step_func(function(reason) {
    assert_unreached(reason);
  })).then(function() {
    t.done();
  });
}, 'Skipping forward through transition');


test(function(t) {
  var div = addDiv(t, {'class': 'animated-div'});
  var eventWatcher = new EventWatcher(t, div, 'transitionend');

  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];

  // Unlike in the case of CSS animations, we cannot skip to the end and skip
  // backwards since when we reach the end the transition effect is removed and
  // changes to the Animation object no longer affect the element. For
  // this reason we only skip forwards as far as the 50% through point.

  animation.ready.then(t.step_func(function() {
    animation.currentTime = currentTimeForFiftyPercentThroughActiveInterval();
    checkStateAtFiftyPctOfActiveInterval(animation);

    animation.currentTime = currentTimeForStartOfActiveInterval();

    // Despite going backwards from being in the active interval to being
    // before it, we now expect a 'transitionend' event because the transition
    // should go from being active to inactive.
    //
    // Calling checkStateAtActiveIntervalStartTime will check computed style,
    // causing computed style to be updated and the 'transitionend' event to
    // be dispatched synchronously. We need to call wait_for first
    // otherwise eventWatcher will assert that the event was unexpected.
    eventWatcher.wait_for('transitionend').then(function() {
      t.done();
    });
    checkStateAtActiveIntervalStartTime(animation);
  }));
}, 'Skipping backwards through transition');


async_test(function(t) {
  var div = addDiv(t, {'class': 'animated-div'});
  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];

  animation.ready.then(t.step_func(function() {
    var exception;
    try {
      animation.currentTime = null;
    } catch (e) {
      exception = e;
    }
    assert_equals(exception.name, 'TypeError',
      'Expect TypeError exception on trying to set ' +
      'Animation.currentTime to null');
  })).catch(t.step_func(function(reason) {
    assert_unreached(reason);
  })).then(function() {
    t.done();
  });
}, 'Setting currentTime to null');


async_test(function(t) {
  var div = addDiv(t, {'class': 'animated-div'});
  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];
  var pauseTime;

  animation.ready.then(t.step_func(function() {
    assert_not_equals(animation.currentTime, null,
      'Animation.currentTime not null on ready Promise resolve');
    animation.pause();
    return animation.ready;
  })).then(t.step_func(function() {
    pauseTime = animation.currentTime;
    return waitForFrame();
  })).then(t.step_func(function() {
    assert_equals(animation.currentTime, pauseTime,
      'Animation.currentTime is unchanged after pausing');
  })).catch(t.step_func(function(reason) {
    assert_unreached(reason);
  })).then(function() {
    t.done();
  });
}, 'Animation.currentTime after pausing');

done();
    </script>
  </body>
</html>
